{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch_geometric.nn import Node2Vec\n",
    "import os.path as osp\n",
    "import torch\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.manifold import TSNE\n",
    "from torch_geometric.datasets import Planetoid\n",
    "from tqdm.notebook import tqdm\n",
    "from torch_geometric.datasets import TUDataset\n",
    "\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Node2Vec for link prediction\n",
    "\n",
    "In this tutorial, we use the node embedding produced by Node2Vec, then we compute the edge embedding $(emb(E))$ as follow:\n",
    "\n",
    "$$\n",
    "emb(E) = emb(u,v) = \\frac{1}{2}(Emb(u) + Emb(v))\n",
    "$$\n",
    "\n",
    "given the edge embedding we predict the binary label of the node using RandomForestClassifier\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## step 1\n",
    "Build a dataset different from cora :)  \n",
    "  \n",
    "We use AIDS[1][2] a dataset representing 2000 moleculas compounds, each moleculas is represented as a graph and each graph has an attribute indicating if the compound is active or inactive against HIV. \n",
    "\n",
    "\n",
    "<sub>[1] Riesen, K. and Bunke, H.: IAM Graph Database Repository for Graph Based Pattern Recognition and Machine Learning. In: da Vitora Lobo, N. et al. (Eds.), SSPR&SPR 2008, LNCS, vol. 5342, pp. 287-297, 2008.  \n",
    "[2] AIDS Antiviral Screen Data (2004)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "device = \"cpu\"\n",
    "dataset = \"AIDS\"\n",
    "data = TUDataset(\".\", name=dataset)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIDS(2000)\n",
      "number of classes:  2 \t\t(active),(inactive)\n",
      "number of features:  38\n",
      "number of edge labels:  3\n"
     ]
    }
   ],
   "source": [
    "print(data)\n",
    "print(\"number of classes: \",data.num_classes,\"\\t\\t(active),(inactive)\")\n",
    "print(\"number of features: \",data.num_features)\n",
    "print(\"number of edge labels: \",data.num_edge_labels)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0 1]\n",
      " [0 5]\n",
      " [1 0]\n",
      " [1 2]\n",
      " [1 6]\n",
      " [2 1]\n",
      " [2 3]\n",
      " [3 2]\n",
      " [3 4]\n",
      " [3 7]]\n"
     ]
    }
   ],
   "source": [
    "data1 = data[1]\n",
    "\n",
    "# extract edge list\n",
    "edge_list = data1.edge_index.t().numpy()\n",
    "print(edge_list[0:10])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1. 0. 0.]\n",
      " [1. 0. 0.]\n",
      " [1. 0. 0.]\n",
      " [1. 0. 0.]\n",
      " [0. 1. 0.]\n",
      " [1. 0. 0.]\n",
      " [1. 0. 0.]\n",
      " [1. 0. 0.]\n",
      " [1. 0. 0.]\n",
      " [0. 1. 0.]]\n"
     ]
    }
   ],
   "source": [
    "# extract edge attributes\n",
    "edge_attr = data1.edge_attr.numpy()\n",
    "print(edge_attr[0:10])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(0, 1, {'label': array([1., 0., 0.], dtype=float32)}), (0, 5, {'label': array([1., 0., 0.], dtype=float32)}), (1, 2, {'label': array([1., 0., 0.], dtype=float32)}), (1, 6, {'label': array([0., 1., 0.], dtype=float32)}), (5, 4, {'label': array([1., 0., 0.], dtype=float32)}), (5, 8, {'label': array([1., 0., 0.], dtype=float32)}), (2, 3, {'label': array([1., 0., 0.], dtype=float32)}), (3, 4, {'label': array([1., 0., 0.], dtype=float32)}), (3, 7, {'label': array([0., 1., 0.], dtype=float32)}), (4, 9, {'label': array([1., 0., 0.], dtype=float32)}), (4, 10, {'label': array([1., 0., 0.], dtype=float32)})]\n"
     ]
    }
   ],
   "source": [
    "import networkx as nx\n",
    "\n",
    "# build the graph\n",
    "graph1 = nx.Graph()\n",
    "\n",
    "for i in range(len(edge_list)):\n",
    "    u = edge_list[i][0]\n",
    "    v = edge_list[i][1]\n",
    "    graph1.add_edge(u,v,label=edge_attr[i])\n",
    "    \n",
    "print(graph1.edges(data=True))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAb4AAAEuCAYAAADx63eqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAABLT0lEQVR4nO3deXyM5/rH8c9MdlnJZkkICYLal1KKWNtULS0HFXWqSmut0mOt0tZR2lLtsVU5amtK1KGl6kdqKdJDEFUiYkuCbIgkss32+yNnRiILkklmklzv18tLO/PMkyvEfOd+nvu+boVOp9MhhBBCVBFKUxcghBBClCcJPiGEEFWKBJ8QQogqRYJPCCFElSLBJ4QQokqR4BNCCFGlSPAJIYSoUiT4hBBCVCkSfEIIIaoUCT4hhBBViqWpCxCll5yeTUh4HJHxqaRmqXGytcS/phND2nrh6mBj6vKEEMKsKKRXZ8UVEZvCikPRHI5KAiBbrTU8Z2upRAd0b+zO+G5+tPR2MU2RQghhZiT4KqjNYddZuDeSLLWG4v4GFQqwtbRgTqA/QR19yq0+IYQwV3KpswLKDb2LZKq0jz1Wp4NMlYaFey8CSPgJIao8mdxSwUTEprBwb+QThV5emSotC/dGci4upWwKE0KICkKCr4JZcSiaLLWmRK/NUmtYeSjayBUJIUTFIsFXgSSnZ3M4KqnYe3rF0engt0tJ3EnPNm5hQghRgUjwVSAh4XGlPocCCDld+vMIIURFJZNbKpDI+NR8SxZKIkutJfJ2mpEqEkJUJLLmN5cEXwWSmqU20nlURjmPEKJiKH7NbzzLDkRVqTW/EnwViJOtcf66nGytjHIeIYT5e9ya36z/heD+CwkciUquEmt+5R5fBeJf0wkby9L9ldlaKvGv5WikioQQ5uzhmt/iG11A/jW/m8Oul0t9piLBV4EMbutV6nPogMFtSn8eIYR5kzW/RZPgq0DcHGzo1sgdhaJkr1coIKCxe5W6iS1EVSVrfosmwVfBTOjuh62lRYlea2OpZHx3PyNXJIQwN7Lmt3gSfBVMS28X5gT6Y2f1dH91Sp0au4u/0MBF5jMJUdnJmt/iSfBVQEEdfZgT2AQ7K4vHXvZUKMDOyoL5A1rQvnoWL7zwAmlpso5PiMpM1vwWT4Kvggrq6MMPYzvSt6knNpZKbB+Z7WlrqcTGUknfpp78MLYjr3eqz+rVq2nevDl9+/YlNTXVRJULIcqarPktnlz3qsBaeLmwOqgdd9KzCTkdR+TtNFKzVDjZWuFfy5HBbfJ3Y1AqlaxcuZKJEyfSp08ffv31V5ydnU34HQghyoKs+S2eBF8l4Opgw7iuvk90rEKh4F//+hdTpkyhd+/e7N+/HxcXl7ItUAhRrnLX/MaX6nJnZV7zK5c6qyCFQsHy5cvp3LkzM2bMQFfSqV9CCLMka36Lp9DJu16VpdPpyMnJwcam8HV9J0+epH379uVclRDCGMZuPMn+CwmUZOGvQgF9m3qyOqhdGVRmejLiq8IUCkWRobdt2zaeffZZ+vXrV85VCSFKKz09net716DQlmySi62lRaVe8yvBJwoICQnh008/5ddff2XYsGGmLkcI8RRiY2Pp0qULXtW0fNi/+VOv+bWzUjIn0J8WXi5lU6AZkMktIp8dO3awYMECPvroI3r37m14XKVSYWVVOWd4CVFZ/Pe//2XQoEG8++67TJ8+HYVCgaWlRbG7M+gpFLkjvaqwO4Pc4xMGO3fuZN68eXzyyScMGDAAjUZDSkoKS5cu5d69e6jVar755htTlymEKMQPP/zAxIkTWbduHf3798/33Lm4FFYeiua3S0koeLgVEeTO3tSR28d3fHe/Sj3S05PgEwDs3r2buXPnMn/+fF555RU0Gg3Hjh3jP//5D1FRUSxZsoQ5c+bg6+vL559/bupyhRD/o9Pp+Pjjj1m3bh27du2iVatWRR77pGt+Kzu51Cn46aefmD9/PnPnzuWVV14B4M8//2TXrl1Uq1aN4OBgHBwcGDduHFu2bCEnJwdra2sTVy2EyMzM5M033+TKlSv88ccf1KxZs9jjn2bNb2Umk1uquPv37zN27Fjefvtt/va3vxke37RpEzk5OcycORMHBwdiY2P55Zdf6NGjh4SeEGYgNTWVgIAAtFothw4demzoiYck+Ko4Z2dntmzZQnJyMgBarZaIiAj27dvHxx9/jIODA4mJiRw6dAiNRkOLFi1MXLEQVcvKlSsJCwsr8LiVlRUTJkzg+++/x87OzgSVVVxyj08UcP78eSZPnkxoaChXrlzht99+4+TJk/To0YOhQ4eaujwhqowvvviCGTNm0L9/f5YsWYKfX+VdW1eeJPhEoYYPH05iYiJ37tzh5Zdfpm3btgwcOBDIvZmuKOk28EKIJ5aeno6DgwPTpk0jIyODDz/8UC5pGoEEn8gnb6gdO3YMJycnnnnmGcNjEnpClA+dTodGo8HS0pL09HRGjRpF8+bNee+993BycjJ1eRWaBJ8o4NFwy8rKwsbG5rGBFxkZib+/f1mXJ0Sll/ffoFarRalUcuPGDcaMGcOrr77K3//+d2xtbU1cZcUlk1tEAY8G3JkzZ1ixYgVabeFbnOh0OlJTU1m6dCmdOnUq8jghRNGOHz9OQkICarUahUKBRqMBcvfR1Gq11KtXj/nz5xMcHMy+fftQq42z2WxVJMEnHqtTp0506tSpyBGfQqHAycmJoUOHkpGRwbffflvOFQpRsemXFM2ePZshQ4YAYGFhYXheqVSi0+no3LkzY8aMYc2aNZw8edJU5VZ4EnziibRt27bYS50nT57k6NGj9O7dm8DAwHKsTIiKLTQ0lEuXLnHu3DnWrVtHVlYWI0eONDyvH9np//0FBQXRoUMHZsyYwf37901Sc0UnwSee2qOXMk+dOsWuXbtITU1l8uTJeHl5yea2Qjyh2rVr06BBA5KSkgDYu3cvsbGxzJ07FwBLS0vCwsLYvHmz4TXXr19n1qxZODs7m6Tmik5alomnpr/R7uHhQUJCAjt37iQjI4MpU6ZQt25dmfkpxFOwsbEhOTmZixcv4u7ujkKh4Pvvv6ddu3YEBATQs2dPnJycqFGjhuE133zzTZF7aYrHkxGfKJFNmzbh7e3N119/jUKhYPz48fj4+EjoCfGU6tevz4ABA5g+fTrXrl1DrVZTq1Ytpk6dyq1btwBo2rQpgYGBhispEnqlI8EnSmTu3LlMmTKFFStW0L9/fxo2bCihJ8RT0gfZmDFj6NGjB9OmTWP//v1kZGSwf/9+0tPT8x0v/76MQ9bxiaeWN+CmT5+OhYUFixYtMjxW1D9O/XokIaqqwj4c5v138dVXXxEdHc3Jkydp2rQp69atM0WZlZ4EnyiRvP+AMzIyqFatGpmZmYU2y9Ufq/9ddnMXVYl+1Obg4FDkMY9+KIyKiqJRo0aFPidKT/40RYnk/dRarVo1Tp48yQcffIBKpcp3nFarRaFQcOHCBRYvXsy4ceMYMmQId+7cKe+ShSh3+/fvp0OHDgQFBfHee+9x9+7dQo97NNj0oafT6ST0yoD8iQqjaN++PVOmTMkXiPpPquHh4fz9739HoVDw6quv0qpVKwYOHEh2drYJKxaibCUkJPDZZ5+xatUq1q9fz40bN1i+fDl//fUXwBMt+ZF7emVDgk8Yjbe3N5aWD1fIKJVKzp8/z5tvvsno0aN5//336dOnDxMnTqRFixYyM01UajY2Nty/fx83Nzdq1KjB0qVLSUpK4tdff+XOnTuGUAsLC5MPgeVMgk+UmQcPHrBy5UpGjx7N22+/bbhks2jRIhITE6XXoKi0dDodLi4uvPzyyxw7dozU1FTq1avHG2+8wdGjR4mKigJy+3MeOXJEPgSWMwk+UWYsLCx48OAB3bt3Nzy2cOFC9u3bx+rVq7G0tJQOL6JS0o/mGjZsSEREBMePHycnJ4f27dvTsWNHlixZAsBzzz3HP/7xD1OWWiVJ8IkyExMTw/nz5w2z2ebNm8f27dsJCQnB1dUVjUZjmO0JBVuhCVFR6X+mhw0bhpeXFwcOHGDbtm0ANGjQAF9fX/nQZ0KynEGUqRUrVrBs2TJ69OjBuXPnCAkJwcvLK98xarUarVbLO++8wwcffICPj49pihXCiPSTuzIyMgwf+HQ6HefOnWPLli08//zzpi6xypLgE2Xu3Llz1KhRA2dnZxwdHVGr1YZJMHnXKLVr147atWuzY8cOWecnKgyNRpNvC6G88q53zczM5NSpU/j7++Pu7i6djkxILnWKMteiRQu8vLxwdHTk/v37hYbeokWLqFevHjNnzpTQExXCl19+SUZGBhYWFoZNYx+lDzadToednR3PP/887u7uhvWtwjQk+ES50Ol0xMXF0bt3b9LS0lCpVIbQW7x4MceOHeMf//gHHTt2NHGlQhQvIyODXr16sWDBAsO+eRYWFsXeo3405GRRumnJn74oFwqFAi8vL3bs2IGjo6NhVLdkyRKOHDnC3Llzad++vbwhCLN37do12rRpw+XLl7GwsGDy5MnAw13S9a5fv15kpxZhWvIuI8qNRqPB29vb8P+fffYZhw4dYu7cuXTo0EFCT5i10NBQsrKyaNasGTNmzMDNzY0lS5Zw+vRpli1bBjwc2d2+fZstW7YU2F1BmAeZ3CLK1YMHD7hy5QpnzpwhODiYuXPn0qlTJ5RKJQ8ePCA2NhZ/f39TlylEPlu2bGHkyJEkJibi6uoKPAy5//73v7z11lt88cUX9OrViz///JPmzZvz4MED7O3tTVm2KIJ8xBblSqlU0q9fP1atWpUv9CC3i3337t0JCQkxcZVC5Ofr60ujRo24dOlSvrWnAB06dGDZsmVMnTqVtm3b8t1336HT6ST0zJiM+ES5y8rK4sKFC7Rq1arA5c2IiAj69u3L8uXLGTp0qIkqFKKgnTt3Mm/ePDZt2kSrVq3yLWOIj4+nWbNmDB48mDVr1pi4UvE4EnzC7Jw7d46+ffuydOlShg8fbupyhDBYvnw5W7duZe/evbi6uhq24XrnnXdo1KiRof2Y7KFn3iT4hFk6f/48ffr0YcmSJQQFBeV7Ljk9m5DwOCLjU0nNUuNka4l/TSeGtPXC1UGa/YqyNW3aNCIjI9mzZ4/hsdTUVJycnIDCd1kX5kWCT5itCxcu0Lt3b/75z38yatQoImJTWHEomsNRSQBkqx+um7K1VKIDujd2Z3w3P1p6u5imaFHpPNqZJTMzkzfeeAN7e3vWrVtX5HHCfEnwCbMWGRlJr1696P/eZ/yWUp0stYbifmIVCrC1tGBOoD9BHX3KrU5ROR07doyPP/6Yn376KV9Hofj4eEaMGMFLL72Ep6cnI0aMMGGV4mnJRWhh1vz9/Zm68kf23LYhU1V86AHodJCp0rBw70U2h10vlxpF5bRp0yYGDRrElClTCrTRq1mzJkuXLuWrr77i9OnTJqpQlJSM+IRZi4hNYdjaMDJVhfdCLI6dlQU/jO1ICy8X4xcmKi2tVsvcuXMJDg7mp59+olmzZkUem5ycjJubWzlWJ4xBRnzCrK04FE2W+ulDDyBLrWHloWgjVyQqswcPHjBkyBCOHDnCH3/8UWzoARJ6FZSlqQsQoijJ6dkcjkp67OXNouh08NulJO6kZ1eY2Z4yI9C4nmYG8M2bN+nfvz/PPPMMW7duxcamYvzMiKcnIz5htkLC40p9DgUQcrr05ylLI0eOxMrKiv3796NQKNi1axcvvPACvXv35syZMwCP3a07OzubK1euEBkZSUZGxmO/ZmW/wxERm8LYTafovDiUZQei+M/ZW4RGJvKfs7f48kAUzy0OZdzmU0TEpgAQHh5Ox44dGTJkCBs2bJDQq+RkxCfMVmR8ar4lCyWRpdZy9NwVnnVKo1q1atjb2xt+t7KyMovR1b179/j222/p06cPAM7OzowcOZJPP/2UmJgYWrduXeRIUP/4xo0b2bJlC3FxcQwcOJA5c+ZQvXr1Qr+efnH1L7/8wsaNG9HpdMycOZNWrVoVOO+5c+e4evUqbdu2zddg3JxtDrvOwr2RRc4Azvrfz9T+CwkciUrmxVpZbJr3FmvWrOGVV14p52qFKUjwCbOVmqU2ynlORvzFqLXvk5GRwYMHDwy/6/spPhqIpfk973/b2dk9UfcOtVpNvXr1gNxQ6tKlC5aWlvzwww9Uq1YNKLifm55CoeDmzZvs2LGDxYsX8+yzzxIQEEBoaCivvvpqoYGpVCq5cOECwcHBdOnShbt377J582Y8PDyoXbt2vmMjIyMZNmwYH374IfPmzTOLDwrFyQ29i2SqHv+BST8DeMdVLVNX/MgrrzxfDhUKcyDBJ8yWk61xfjwDewWwbO3UAo+rVKp8Qfgkv9+6deuJj83KysLW1jZfOPbs2ZN//vOfODo6Arkjq6ysrHyX1iwtLdFqtaSmpuLg4FDk96UPtT179tCxY0fq1KkDQMuWLUlISECtVht2u9fTj/Y2b95M48aNmTBhAgCdO3cmMjKS2rVrG86rUqn429/+xunTp6lZs2ap/x7KWkRsCgv3Rj5R6OWlsLRh0/kH9HsuRWYAVxESfMJs+dd0wsYyvlSXO20tlfjXciz0OSsrK1xcXHBxcSnx+Yuj1WrJzMzMF4g2NjbY2dnlO0alUhk6+etHVPrH9SO+4iQlJeHg4GAIUxcXF7KyssjOzi4QfHrnzp1jzJgxhpDTf728Nei7kNy8eZPGjRub/WjPGDOAVwe1M3JVwhxJ8AmzNbitF8sORJXqHDpgcBsv4xT0lJRKJfb29sVuT6NWq/MFnH7Lm6ysLHJycrC2tjY8DoXP+tTpdCiVSsPjmZmZeHh4FNs+Ky0tDXt7e8NrrKysigxJtVqNra1tvsfCw8OZN2+eUS4NG6PNV1WcASxKToJPmC03Bxu6NXLn/y4mlOgNTaGAgMbuZv1GptFoUKvVhuDTB5uDgwM2NjY0adIk3/F5Q0//382aNeOXX34xBNeFCxfo3LlzgbDK+xr96FClUmFlZUV8fDzu7u6GmiwsLAzHZmdnFzhX3bp1eeeddwq9zJuUlMT169cLvfxb2GPW1talvr96JMkGrbZ0E6H0M4DHdfUt1XmE+ZPgE2ZtQnc/Qi/cQs3TjwpsLS0Y392vDKoyHpVKhUajyTeymzVrFqGhoZw8eZKAgAAmTZrEK6+8wvHjx8nOzqZr1675RkkvvPACn3/+OceOHcPNzY0bN27QsWPHQr+ePswCAgI4cOAAvXv35s6dO1SrVs1wH09/7rz3+h4NPnd3d/r161fq718/un2a+6xpaWkkJCTke/yqx/Oo3Jo8/gsWI0utJfJ2Wqm/p8Kkpqby6quvUq1aNbZs2YKDgwMXLlxAq9VSr149wwcRUT4k+ITZ0ul0/LBqCcqIBGzbDjZMQ38SdlZK5gT6m/1khZycHCB/B5AxY8YQGBhIVlYWd+7cwdc3dwRibW1NdnZ2gUuddnZ2zJo1i48++oj09HSWL19uGL2NHz+e4cOH8/zz+Wcsvvnmm7z++usMGDCA1NRU3nvvPWrUqAHAsmXLGD9+vGHCTU5OTqGjR2NQKBTY2dlhZ2eHq6tric8z+ruThEYmlrqeXb/s58L6GdStWxdvb2/q1q1r+OXl5VXiP4c7d+5w4cIF/vWvfxkmLK1atYo9e/ag0Wi4cuVKkZeahfHJn7QwS1qtlqlTp3L06FGO/forv155UOzaLL2KtjuDSqUiKSmJNm3aEB4ejkKhwNfX1xB2ebVrV/TEi379+hU6Alu5cmWhx7u4uLBkyRKionLvoQYGBhreeO/evZtvRGlhYWH2C7qNNQO423Md+Jt3c2JjY4mJiSE0NJSYmBhiY2OJi4vDxcUlXxg+Go4eHh6FLmFJTEykfv36DBo0CK1Wi0KhYMaMGUyYMIGgoKAnDr3Nmzezfft26tevz/z588tsYlZlJ8EnzI5Go2HcuHFcuHCB0NBQXFxcCHJ3p4WXCysPRfPbpSQUkG8EqN+PL6CxO+O7+5n9SE+vdu3a3Lp1y/BmmJe+u0rex4tbyK4//kl3/vb398ff37/A4x9//LHhv/ft28e1a9fMfjRirBnAHf29ebGIe3xarZaEhARiYmIMYRgTE8Pvv/9u+P+UlBS8vLwYP348EydONHxguHnzZr5JTgqFAi8vL/bu3Wu4zPm4XduPHz/O119/zYcffsjBgwf57LPPWLhw4WO/r/Pnz2NtbU3dunXLbORe0Zj3T7OoclQqFaNGjSI+Pp79+/fnW8fWwsuF1UHtuJOeTcjpOCJvp5GapcLJ1gr/Wo4MblNxd2Av7A2vsIArbiF7SZYbFBau+jdgnU5HXFwcY8aMMSywN1flMQNYqVRSq1YtatWqxbPPPlvoMZmZmcTFxWFpaZlvK6O4uDhDc4C87eJu3bqFp6dngccfFR8fz88//8zw4cMJDAykTp06jB49moULFxbb3/X3339n1qxZpKWlUbNmTUJCQgqsDVWr1Zw9exZvb29DLeXpafqpGosEnzAb2dnZDB06FJVKxZ49e/Ktd8vL1cFGZt4ZSWFvmPoQVigUjBkzprxLKhFzmQFsZ2dHw4YNCzweFxeHl1fBUI2JiTE0HiiMPtT0o8n+/fsDkJGRQfPmzbl69SoNGjQo9DVRUVGsX7+eqVOn8sorr7Bo0SI+++wzFixYkC8s79+/z7hx40hOTubEiRMFuveUlYjYFFYciuZwVBJAvtG6rWU8yw5E0b2xO+O7+dHS28WoX1uaVAuzkJGRQf/+/bG0tGTnzp1Fhp4QRZnQ3Q9by5KtCSzrGcA3b97Ex8cHIN+yi6SkJOrXr5/v2MJGfsnJyVhZWeHs7Azk9nd1cnJCrS7Y1k//+vDwcFQqFS+88AKQO4FK3/RcX4NGo8HV1ZWNGzfSoUOHcrsUujnsOsPWhvF/FxPIVmsLXKLO+t9j+y8kMGxtmNE3lZbgEyaXmprKCy+8gKenJ8HBwYap/UI8jZbeLswJ9MfO6une1spjBnBSUpJhxGdlZcWZM2c4fPgw58+fN1xezDvSfpSFhQXW1taGULtx4wb29vaFTm7RHxMVFYW7u7thjWhCQoKh0fijX+vUqVM4OTmVyySmh/1Ui5+oBg/7qS7ce9Go4SfBJ0zq7t279O7dm2bNmrFhwwazn0QhzFtQRx/e7+0H6mwed8dToQA7KwvmBDYp0xnA2dnZpKam8txzzxkeO3PmDPPmzePmzZu89dZbzJs3D61Wy9GjR9m5cycPHjz4X42530WXLl04ffo02dnZAPzyyy/Ur18fDw+PIr9uUlJSvh01rl+/brhX+2i4xsfH4+bmlu++ZFlsXVXSfqqZKi0L90ZyLi7FKHXIu4wwmYSEBPr06UOvXr34/PPPzb4XpKgYbh/ZRpNbMdR78S2zmAGcnp7O9evXGTRoEL/88gtWVlaMHj2a0aNHG47JzMzEwsICb2/vAhNjAKpVq0ZgYCBTpkyhevXq2NvbExgYmPv93r6dr0WdfjRXp04d0tPTDffzzp8/z4ABA4CHHXv0bt26Rb169fJ98Lx//z4jRowgLi6u0KUbdevWpXbt2k/1YdVc+qlK8AmTiIuLo1evXgwdOpT58+dL6AmjiIuLY9myZZw8eZL69eubxQxgV1dXYmJiSE9PN4TNo8tP9Pe0fXx8DPcCHzVhwgSee+45rl69So8ePahVqxYAS5cuJTAwkICAAODhaG7w4MH84x//4ODBg9y5c4c6derQpk0bgHx1QO46w2effTbf7GJnZ2c2bdrEjRs3DEs4YmJiOHv2rOG/ExMT8fT0LHJdo7e3NzVq1EChUJhVP1WFrrJvxSzMztWrV+nVqxdvv/02//jHP0xdjqhEgoKCqF+/fr61iFXZ119/TUhICADffPMNjRs3BmDUqFGsXr3aELgvvvgi7733Hr17936q86tUKm7dupUvGPOucYyJiUGlUlG3bl3s2w3kbp2OaBUlH2/ZWiqZ2rtRqWd1y4hPlCutVsv333/PtGnTDHvBCWEMx48f5/Dhw6xZs8bUpZSpR9ftFbeOb9KkSUyaNKnA6wcOHJhv5rRKpSpRyzgrKyvq1atX7DrP1NRUYmNjWbD/Osml7CpnrH6qEnyiTFy/fp2aNWsWmB6tVCqZMWOGTGIRRqXVapkyZQqffvppsdtAVQaPhlxxtwn0yxbyXsJUKBQMGjQIyF28vmPHDtLS0gxLJYzNycmJZs2a4XAqAxJL3081NUtV6nPIrE5hdJs2baJJkyZs2LCBzMzMAs9L6Alj27hxI1ZWVrz22mumLsWsKJXKQrsC6e9wpaWl8ddff9GzZ09Dk/KyYqx+qk62Vo8/6DHkHUgYXUZGBp9//jm7d+/GwcGBIUOGmH2TY1FxpaamMnv2bHbt2iWTpJ6Q/s+pevXqfPTRR+XyNY3VT9W/Vum3cJLgE0Y3btw4IHeG2meffYa1tTUDBw6UhemiTKSnpzN27Fjat29v6lJEMcqjn+qTkuATRqe/2f7SSy+hUqlYvnw5NjY2BAYGFlifJERp1axZkw8//NDUZYjHMJd+qiDBJ8pA3stNAwcOJCsri6+++gpra2t69+4t9/jMlCm65BvDk27DJExvQnc/jl5OJlP19IvYjdlPVdbxiVLZvHkz3t7eODk50bp16yKnVq9fv57g4GBmzJhBQECAvFmZkeK75Od2OSmrLvlP4+jRo9SrVw8vLy/5+anAHvbqfPJ7fbn9VI3XWk5+ekSJTZ06lWXLlhEaGsqgQYPYsWOHIfQ0mtxPdPru8aNHj+bvf/877777LlevXjVZzSI/U3fJf1KXL1+mW7duTJw4kV27dnHv3j2T1CFKL6ijD3MCm2BnZcHj5iKVVT9VueYkSiQ2Npbw8HAOHDhA9erV6dy5MxMmTMDCwoKBAwcaPpGfPXuWdu1ye+vl5OTw7LPP4udXdtu/iCf3NJ+883bJB8q0qXNhbt++zYYNG3Bzc+OLL75g3759jBo1itatW8sWVhVQUEcfWni5sPJQtEn6qcqlTlEiOTk5vPvuu7z22ms8++yzWFlZ8dNPPzFu3Dj+85//0KFDB2JiYhg7diw///wzlpaWZGVlldt+X6J4EbEpDFsbVqJ7LXZWFvwwtmOZN3cuSmpqKkuXLuXw4cN069aN4cOH4+vrK/eOKyhT9FOV4BMlNnPmTNLT01m2bJlhtubq1avZt28fW7Zswd7e3nDPT6vVyn0ZMzJ206lSza7r29TTKF3yn4a+sbP+5+jixYt8/vnn3Lx5kyFDhvDSSy9Rs2bNcq1JVEzyTiSemv6z0kcffcSlS5eYPn066enpQG5H+Bo1ahhGdvp7fhJ65sOYXfLLk0KhQKlUGgKwSZMmrFu3jtmzZxMcHMzUqVPLZA85UfnItQHx1BQKBRqNBmtra3788UeGDh3K7NmzadiwISdOnCArK8uwN5gwPyHhcaU+hwIIOR1X6i75T0N/1UD/YUr//127dqVt27Z07txZOreIJyIfw0WJWFhYoNFocHR0JDg4mOeee4579+7h5eXFjz/+CJTNDs6i9CLjU0vVNgqM1yW/OBEREezdu5edO3cCD68aqFS5TYpPnz5tOHbWrFm8/PLLZVqPqDzkHp94IkXdo3vax4Xpjf7uJKGRpe+S39Pfg3WjyqZN2OnTpxkyZAhBQUHs2rULa2tr5s+fb9h1PCMjg1deeYXNmzfj5uZWJjWIykvemUSh4uLi+Pjjj5k/fz5JSUlFHqcPt7yfn/JOQBDmx5y65Bdl+fLlTJkyhQULFnD27Fneeecdpk+fzuDBg7l//z7VqlVj3759uLm5GbbeEeJJybuTKCA5OZk+ffoAkJCQwOjRo9m9ezcpKSnAw8XpeeW9tyL3Wcxbbpf80v3TN1aX/KK0b9+emJgY7t+/D8Abb7zBhQsXcHJyYtmyZfmOlQ9Z4mnJT4woIDIyEl9fXz744ANWrVrFgAED2LNnD4cOHSInJwcLCwtSU1M5fvx4oSEozNvgtqXvbm+sLvlF6du3L7dv3+b7778nJyfH8PjixYs5ceIEiUbY0FRUXRJ8ooDmzZujUqnYv38/AGPGjKFdu3bs2LHD0Crq4MGDxMXFyezNCkjfJb+kA3NjdskvSsOGDZk0aRLbt2+nX79+/Pzzzxw9epQNGzaQk5ODh4dHmX1tUfnJ5BZRQE5ODp9//jkKhYKBAwfSpEkTAF5//XXs7e1ZtWqViSsUpVWROresXbuWgwcP4urqSnJyMh9++CFNmzYtsiG6EI8jwScK9ddff/H111/j6+tLt27d6NChA7/99hv79+/nn//8p7zhVAIl6ZJvrYR5Lzcrl16dGo3GcEVBpVKhVqsNfTkl9ERpSPCJIp0+fZodO3Zw5swZGjZsSEhICIsXLyYoKMjUpQkjyQ2/SDJVanKXpRdOoQAbSyVzA5sw4tl65Ro6+qUxEnbCWCT4RLHu379PXFwcv//+O40bN6Z79+6mLkkY2bm4FD758SSn47OxtLAotEt+90buTAjww9+jGtbW1qYrVggjkOCrwmSRucjrTno228NjuRSfzv0sFc55uuTXsLcmNDSUTZs2sWHDBlOXKkSpSPBVQT/++CNNmjShSZMmEn7CQP+zsG7dOt58880CzyclJdG8eXPCwsLw8fExytfU6XT89ddfREVF0bVrVywsLKhevXq+Yy5fvoyfn59c5hRGI02qq5ilS5cye/ZsGjVqxPbt22ncuHG+SQR6hT0mKjd9sHzzzTe0aNGCBg0aEBYWxtmzZzl79iwJCQkolUqSkpKMFnwTJkwgMTGR27dvs2XLFjp27EiXLl1o27Yt1tbW7N+/n/DwcGbNmmWUrycESPBVGVqtlvT0dKKjozlw4AB//vknI0eOZNu2bfj4+OQLuhMnThAXF8eQIUNMXLUoT/rJI/7+/vTt2xdfX1/s7Oxo1qwZgYGBPPvsszRt2jTfsaWRnJzM77//zrlz5wA4cuQIu3fvZv369WRkZNCzZ0/69OlDly5dSv29CZGXBF8VcffuXdzc3Fi4cCFWVlZ06dKFpKQkhg8fTkhICHXq1DEcm5KSQrdu3UxYrTAFfZC1b9+e6Ohojh07VuhxxppdqdFo8PT0ZPPmzbz22mt07dqVrl278u233/L3v/+d7777jh49ehiWMAhhLBJ8VcBrr72GjY0N//73v3FycjKM7ObOnUtGRgYjRozg0KFD/PLLL9SrV48XX3wRkLVSVY3+7/rFF180dOjRarWGn4O8v4zB09OTGTNmsHv3bpycnGjfvj21atVizJgx6HQ6zp49S48ePeRnUBidzGqo5O7fv89ff/2Fs7Mz8HAfPch9o/v000/p2rUrDg4OTJs2jdq1axteK284VZOvry8zZ84EchtAW1hY5NsA1pg6duyIr68vP//8M5s3b2bLli1cunSJjRs3GpZNyPw7YWwSfJWcs7Mzu3bt4s8//zRMELCwsECr1RreyFJSUmjcuDEnTpzAxcVF3mgEYWFhJCQklPnXcXBwYMqUKbz++utkZGRw6tQp3nnnHTp16sTEiRMB+QAmjE8udVYBPj4+rFq1itGjR/PJJ58wd+5clEolGo2Gq1ev8uDBA37//Xfs7OxkNmcVp7+smZCQgIWFBZ6enuXy9bp06ULnzp2B3Ht/+j325HK7KAuyjq8KOX/+PG+++SZDhw7lvffeK/C8rOkTeunp6VhZWWFjU3Y7MOSlDzgJOlEeJPgqOY1Gg1qtNryBnTp1irfffptRo0YxadIkw3ESeqI4Op0OnU4nPyOiUpDgq8RycnIYPnw4zZo1Y8GCBYZP0seOHWPKlCm89dZbpKWlMX36dBNXKqoC+XAlzIUEXyWVmZnJq6++io2NDcHBwQUuWYWGhhIUFMTSpUsZNmyYiaoUFUF6ejp//vknkZGR3L17l6ZNm9KgQQN8fHxKdCk0bwDqL23GxcWRkZFBo0aNjF2+EAVI8FVCaWlp9O/fn9q1a7NhwwasrKwKPS4lJQUXF5fyLU5UKOnp6axfv57o6Gjs7e1xdXXl7NmzxMbG8re//Y0JEyY80XnGjx/PkCFDCAgIADDMKtZfhVi5ciWdOnWidevWZfa9CKEnwVfJpKSk8OKLL/LMM8+wevVqmaEpSuzevXtMnTqV1NRUJk2aRP369XF2dqZ69ercuHGD7t27c+3atcee56OPPuKbb76hbt26+Pn58fHHH1OvXr18x9y5cwdXV9ey+laEyEcuuFciSUlJBAQE8Oyzz/LNN99I6IlSycnJITw8nB9//JGAgAB8fHwMOydcu3aNNm3aGJohFEWlUnHr1i3Wr1/Pnj178PDwoF+/fsybN89wzLp163BycirT70WIvGTEV0ncunWL3r17M3DgQD755BOZEi6MolGjRixduhQ7OzsSEhK4fv06ERERnDlzhrVr1z5RT9eMjAwePHiAu7s7AOHh4SxevJiEhAQyMjJo1aoVa9euLetvRQgDCb5K4MaNG/Ts2ZPRo0cze/ZsU5cjKgH9pJMzZ87w4Ycfkp2dTbNmzXB3d6dhw4YMGDCgyHvHeufPn0epVNK0adNC1+ctXbqUBQsWcOPGDUPHIPnAJsqDBF8Fd/nyZXr16sW0adOYPHmyqcsRlVh8fDw2NjaGRueP6/JTr149pkyZYmiW8OjxQUFBNG/enBkzZshSB1GupGVZBXb+/Hn69u3LRx99VOiO2UIYQ05ODtbW1tSsWTPf48WF3oMHD+jZsydbtmxBpVIxY8YMw/EqlQorKytGjBhh2AlEQk+UJxnxmZnk9GxCwuOIjE8lNUuNk60l/jWdGNLWC1eHh2umwsPDeemll1i2bBnDhw83YcWiMtM3OFizZk2JXn/8+HGWLFlCQkIC77//Pq+88kqBY2S0J8qbBJ+ZiIhNYcWhaA5HJQGQrdYanrO1VKIDujd2Z3w3P9Jj/mLQoEGsXbuWAQMGmKhiUVVERETwzDPPlHiWsEajITg4mBUrVuDp6cm8efNkvZ4wKQk+M7A57DoL90aSpdZQ3N+GQgFWCkg78h0b5r5J3759y69IIUog74SVe/fusWLFCrZt28aAAQM4efIka9euxdvb28RViqpGri+YWG7oXSRTVXzoAeh0kKMFx66jSHJuXD4FCvE/Op0u35ZBeo/+f17nzp0jOzsbnU5H9erVmTt3LiEhIcTExODs7CyhJ0xCRnwmFBGbwrC1YWSqil8EXBg7Kwt+GNuRFl4uxi9MiCdw+/ZtlEplkXv23b17lyZNmnDkyBEaNy74QU2tVmNpKfPrRPmT4DOhsZtO8X8XEx470iuMQgF9m3qyOqid8QsTohBxcXFs376dAwcOcPPmTTw9PWnYsCEtW7YkKCgIOzu7fMdPnjwZjUbDihUrTFSxEIWTj1smkpyezeGopBKFHuRe9vztUhJ30rPzzfYUoiw8ePCAb7/9lnv37vH666/TqlUrHB0d+fPPP1m4cCFeXl6GpQmQu9QmODiYixcvmrBqIQonwWciIeFxpT6HAgg5Hce4rr6lL0iIQugnpyxfvpzo6Gi++OIL3N3dDcsPateuze7duzl27Jgh+HQ6He+++y4ffPCBNJ4WZkmCz0Qi41PzLVkoiSy1lsjbaUaqSIii2djYYG1tne9+XlpaGj/99BNqtZpBgwYZHtdqtWg0Gt555x1TlCrEY0nwmUhqltpI51EZ5TxCFEa/FOGFF17g+PHjTJw4kcuXL5OYmEhqaioeHh689957tG3b1vCajIwMvvzyS5m4IsyW/GSaiJOtcf7onWyLbxQshDE0a9aMf//73wQHB9OuXTuaN29Oo0aNcHR0LHBstWrVaNmypQmqFOLJSPCZiH9NJ2ws40t1udPWUol/rYJvPEKUBScnJ8aOHWtoq7dpbzSpmSocbS1pUsvZ0FZP9oEU5k6WM5hIcno2nReHlir4bCyVHJ/RQ2Z1inKx/9Ql1ofFcSYhByi+rV5LbxfTFCnEE5DOLSbi5mBDt0bulHT7MYUCAhq7S+iJMqfT6dgcdp0JP14m7GYm2WptgQ9sWf97bP+FBIatDWNz2HXTFCvEE5DgM6EJ3f2wtSzZZSFbSwvGd/czckVCFLTljxss3HsRlU5B7iKaoul0kKnSsHDvRQk/YbYk+EyopbcLcwL9sbN6ur8GOyslcwL9pV2ZKHMRsSks3BtJpurpLslnqrQs3BvJubiUsilMiFKQ4DOxoI4+zAlsgp2VxWMveyoUuT065wQ2IaijT7nUJ6q2FYeiyVI/fS9ZgCy1hpWHoo1ckRClJ7M6zUBQRx9aeLmw8lA0v11KIisrE4Xlw3t3+okDAY3dGd/dT0Z6olxIWz1RWUnwmYkWXi6sDmrHlZuJdBw+haAJ//jfDuxW+NdyZHAbL3nzEOVK2uqJykqCz8zcuXkd7/RLLBsqO1QL05K2eqKyknt8ZiY6OpqGDRuaugwhpK2eqLQk+MzM5cuXJfiEWZC2eqKykuAzMxJ8wlzkttUr3VuEtNUT5kiCz8yMHz+enj17mroMIRjc1qvU59ABg9uU/jxCGJNMbjEzzz33HNI+VZgDV3tr6lqmEZVjh0L59J+Rpa2eMFcy4jNDipI28BTCSFJTUxk+fDjxoRuxtZK2eqJykeATQuRz9uxZ2rZtS/Xq1Tn5awgf9GsqbfVEpSLBJ4QAcndhWLNmDb179+ajjz5i1apV2NraSls9UenIfnwmFB0djZ2dHQ4ODtja2mJpaSmbeAqTSEtLY9y4cfz1119s27aNxo0bFzjmXFyKoa2egtzF6XrSVk9UJBJ8JlSrVi2eeeYZXF1d8fT0pE6dOnh5eVGnTh08PDxwdnbGyckJBwcHU5cqKrGIiAiGDBlCQEAAX375JXZ2dsUefyc9m5DTcUTeTiM1SyVt9USFI8FnIklJSQwYMIBt27Zx9uxZrl27xvXr14mLi+PevXuoVCqsrKyoXbs2GzZsMHW5ohLS6XR8++23zJ49my+//JIRI0aYuiQhyoUEnwllZ2djY1P4J2S1Ws2+fftYuXIle/fuLefKRGWXnp7OuHHjOHfuHNu3b8ff39/UJQlRbmQdnwnZ2Nig1WpRKBSGtXs6nQ4LCwssLS158cUXC73XIkRp/PnnnwwZMoQuXbrwxx9/UK1aNVOXJES5khGfGdJqtShLsGBYCL2bN2/i5uaW74qCTqfj/Pnz9OjRg6VLlzJy5EgTViiE6ci7q4lpNBq02tzZcSpVbhf7cePG8f333wNIFxfx1D777DP8/Pz48ccf0Wge7p6uUCjw8/MjPDxcQk9UaTLiM1P6yS1CPK3Nmzdz584dQkNDmT9/Pq1by96OQuQlwWcisbGxLFq0iLNnz+Ln50fjxo1p1aoVHTp0wN3d3dTliQro0UvkM2fOJCEhgYULF1K7dm0TViaEeZHgM4Hbt28zYMAA3n77bTw8PLhy5QoXL17k9OnT+Pr68vnnn1OnTh1TlykqqLwBOHjwYFq3bs27776Lvb29iSsTwjzIrE4TuHTpEo6OjowePbrAc//617+YNGkSP/74IzqdThpWiyLl5OSQlJREjRo1sLOzMwSeUqlEo9FgYWHBP//5T8aNG0fjxo0ZNGiQdAYSApncYhLW1tYoFAq2bdtGZmZmvuecnZ0NYSeDcVGUU6dO0bJlS6ZOnUpQUBCXLl1CqVQafmYsLCzQ6XQ0atSICRMmsHbtWiIiIkxctRDmQS51ljP9KO73339nzZo1pKenU7NmTXx9fbl37x7nzp1j2LBhjBgxQpY1iEKpVCqGDh1KYGAgY8aMYdGiRWzatIljx45RvXp1w9pQrVZrGOEtXLiQ6Ohoud8nBBJ8JnXz5k0iIiI4f/488fHx+Pj4MGTIEGrVqmXq0oSZ0n9wmj59OgEBAbz00ksAvPnmm+Tk5LBp0ybDsfHx8dSsWROAjIwMPDw82LFjB3379jVJ7UKYCwk+ISqg9957j1q1avH+++8bHvP392f69OmMGTOG27dv069fP3777TecnJw4cuQIKSkp9O/f34RVC2EeJPhMTKvVGj7Fy2VN8Tj6n5WLFy/yt7/9ja+++oqAgAAA9uzZw7Zt21i9ejV2dnakp6fLzh5CFELeaU1MqVRiYWEhoSeeiP7eXZMmTXj33XeZP38+J06cAHL31HN2djZsK+Tg4CATpIQohIz4hDBjj1vSsnTpUi5evEh8fDyXLl1i8eLFDBo0qBwrFKLikeAzA/rZmzdu3CAtLY1nnnnG1CUJEwoODsbFxYWWLVsWOdEp74zflJQUjh07RuvWrWXGphBPQILPjGzfvp2srCxGjhwpi9erqPHjx3Ps2DG6d+/O0aNHWbVqFa1bt8ba2vqJXi9LYIR4POncYiKJiYncvHkTW1tbatasSfXq1RkyZIjheQm9qufWrVtER0cbFpp/+umnfPfdd+Tk5PD8888/0Tkk9IR4PBnxlbN9+/Yxe/ZsHBwc8PX1pVq1ajg7O9OuXTtefPFFw8QEUTUNGjSI119/3XCfbs6cOVhYWDBx4kQ8PDzQaDSEhYXRvn37Jx4FCiHykxFfOdLpdLz11lscPnyYatWqkZyczK1btzh79iyfffYZycnJjB071tRlChPJycmhXbt2XL9+nbi4OLy8vHjrrbcYO3YsvXv3xsPDg8OHD3P58mU6d+5s6nKFqLBkxFeObt68Sf/+/QkPDy/wXHZ2Nv7+/ly7ds0ElQlz8fvvv7NhwwZ69uxJ3759qVGjBosWLSI5OZkvvvjC1OUJUSnIiK8cOTk50bVrV7p168b48eOpW7cu9vb2ZGVlcfbsWRo0aADIBIWqrEuXLly9epX//ve/nD59mgULFvDHH3/Qr18/U5cmRKUhIz4T2LVrFwcPHiQ7Oxs7OztSUlLIzs5m8uTJdOrUSWZ0VnJFfbDJ+/ceERHBV199xa1bt/D09GTDhg3lXKUQlZcEn4no7+/duXMHb29v/Pz8TF2SKEPnzp3jjz/+4K233ir2uEc/9CQkJODp6QnIlQAhjEUudZqIm5sbbm5uhv+XUV7ltXPnTsaNG4dCocDe3p7XXnutyBDLuxejQqEwhJ5Op5PQE8JIJPhMSD/YVigUEnqVWGpqKmvWrMHDw4N33nmHdu3a0ahRI8Mu6XrZ2dnY2NgABddxys+HEMYjlzqFKAPh4eEcO3aMyZMnA/DgwQPs7e35+OOPOXz4MAcOHAAejuyOHz/O+fPneeONN7CysjJl6UJUenLtxAzodDru379v6jKEkdy8eZPnnnuOxMREANRqNfb29gC8//77uLq6MmnSJCB3JKfT6bC1tWXQoEESekKUAwk+MxAXF8fWrVtNXYYwkjp16jBr1iwuXLgAgKWlJRqNBgBbW1sWL17M+fPn+eCDD3jttdc4f/48bdq0wd3dXbYREqIcSPCZAaVSSbNmzQDkja+SmD17NlZWVkyZMgUg3708Hx8ffHx8+Pzzz+nevTvNmzc3PCf38oQoexJ8JqDT6dBoNKjVagDc3d3p2rUrIG98lYW1tTWLFy/mxIkThtG8ftS3bt06oqKiOHv2rLSoE8IEZHJLOYmMjMTW1pa6desWmJb+9ddf8/zzz9OqVSvTFCfKzN69e5k5cybbt2+ncePGANy7d4/q1asDsjZPCFOQ4CsnDRo0wNbWFisrK+zs7PD09MTHx4eOHTsyceJEQkNDadmypanLFKV08eJFQys6vYULFxIaGsrBgwfzHSuhJ4RpSPCVA41GQ6NGjfj111+xtrYmLi6Oq1evcuXKFWJiYti4cSP379+nWrVqpi5VlFBOTg4zZ85k586dRERE4OTklO+5UaNG0aBBA3r16kVAQIAJKxVCSPCVg/T0dL755hteeOEFmjZtmu+5+/fv06FDBy5dumSi6kRp3bhxg6FDh+Lh4cGGDRuoUaNGgWNiYmLo06cP9erVY8+ePVhaSu8IIUxFgs/E4uPjCQkJYeLEiXLpqwLavXs3b731FjNmzGDq1KnFTk7K23dTCGE6Enzl4OLFi9SoUSNfs2GdTodWq5UFyxWUSqVi1qxZbN++neDgYDp16mTqkoQQT0iut5SDGTNm4OXlxcqVKwEMo7q8a7tUKpWEYAURExPD0KFDcXV15fTp07i6upq6JCHEU5DgKwfNmzfn559/pm3btqjVaqpXr27YisjX15devXoxduxYVqxYgbe3t6nLrTKS07MJCY8jMj6V1Cw1TraW+Nd0YkhbL1wdbAp9zc8//8ybb77J9OnTmTZtmlyaFqICkkud5USj0XD//n0SEhK4ceMGV65cMczqTExM5PLly1y9ehU7OztTl1rpRcSmsOJQNIejkgDIVmsNz9laKtEB3Ru7M76bHy29XYDcEfmcOXMIDg4mODiY5557zgSVCyGMQYLPDKjValJTUwudDSiMa3PYdRbujSRLraG4n3yFAmwtLZgT6E+3OhYMGzYMFxcXvvvuu3z7KAohKh4JPhNITEzE3d1d2pOVs9zQu0imSvv4g//HWgkPft/I5MA2vP/++3JpU4hKQIKvnKnVahwcHLh//75h01FR9iJiUxi2NoxMleapX2ttASFvd6aFl4vxCxNClDv5+FrOrl+/Tq1atST0ytmKQ9FkqZ8+9ABUWlh5KNrIFQkhTEWCr5xdvnyZhg0bmrqMKiU5PZvDUUnF3tMrjk4Hv11K4k56tnELE0KYhARfOZPgK38h4XGlPocCCDld+vMIIUxPgq+cXb58GT8/P1OXYTa0Wi2zZs1i+PDhXL16tUy+RmR8ar4lCyWRpdYSeTvNSBUJIUxJFrCXg7wLpUM1jYnX+WB3+EqxC6Urizlz5vDzzz/j6OjIhg0bCoS+UqmkefPmbN26lZs3b9KgQYMSfy2NRkNycjIJCQkkJiaSkJBAQkICfyS6gaL03VVSs1SlPocQwvQk+MpQoQulnetzMgn+PBDFsgNRBRZKm7Pjx4+zZMkSkpKSWLx4MV26dCn2+BMnTnDp0iVCQkI4f/4806dP57vvvsPZ2dlwjFar5bXXXuM///kP6enpBc6Rk5NDYmJiviDL+yvv43fv3sXFxQVPT088PT3x8PDA09MTe0cPUJf++3eylZZyQlQGEnxl5HELpbP+d+lt/4UEjkQlMyfQn6COPmVWT2pqKvHx8fj5+aFUKvm///s/fvnlFzQaDYsXL8bW1vax51i4cCGDBw+mYcOGTJw4kV27dlGvXr0Cx+l0OhQKBT/88AO9evWiYcOGNGzYkBkzZnDlyhXatGlT4DUWFhbcvXs332OhoaH07dsXd3d3Q4jpf9WuXZvWrVvne9zd3b3Q7X5WH77CsgNRpbrcaWupxL+WY4lfL4QwHxJ8ZeBpFkrrdJCp0rBw70WAMgm/zMxM/P39sbGx4dSpU7i6unL27Fmys7PZsWMHkydPxtfXt9hzhIWF4eDgwMsvv4ybmxv+/v4cPnyYESNG5Gu2nfs95QZfTEwMnTt3NjzeoEGDIoOvevXq3Lt3L9/WTF27diU7O7vUi8YHt/Vi2YGoUp1DBwxu41WqcwghzINMbjGyiNgUFu6NfKruIACZKi0L90ZyLi7F6DXZ2NjQsGFDDh48aNhJ4I033mDFihW0atWK+Ph4IDewHqV/LCIigmbNmqFS5d7natOmDbGxsaSmphb5GkdHRxITEw2Pu7m5kZCQAOTej8vL1dWVu3fvotU+/HOztLQ0SqcUNwcbujVyp6SNchQKCGjsXunvxwpRVUjwGVlpFkpnqTVlslBaqVTi6OjIn3/+CeSGjr7fpI2NzRMFn7W1NVlZWajVuTfLatSoQUZGBjk5OUW+tkGDBiQkJJCZmQmAnZ0dd+7cAR5uyaQPQHd3d+7du1cgEI1lQnc/bC0tHn9gIWwtLRjfXWbiClFZSPAZkSkWSufk5BAXF0d4eDh79+7l3//+N99++60hbPRq1KiRb/Sl5+bmxu3bt4s8v76fqKenJzk5OYbgUigUPHjwAEdHx3zHwcP9BgMCAoiLi+PMmTNA7ghOf0n16tWrHDt2zLAHoaurKykpKYZgNbaW3i7MCfTHzurpfuTtrJTMCfSXdmVCVCJyj8+IjLVQ+vuwa7zka1PozMVHf6Wnp+Pu7p5v4keDBg0KbGrr6upqGNnlVaNGDUPwFTZq0wdau3bt+PXXXzlw4IDh8mhqairVqlXjwYMH3Lt3j5o1a2JpaWl4TdeuXYmPj2fixIncvXuXV155hREjRgAQHR3NyZMnDfcAW7RowU8//UROTg729val/nMsjP7+6dPuzlCWk46EEOVPgs+IjLVQ+uOv1/HZ2W35wszDwwM/Pz86d+6c7/Hq1as/0X0wDw8Pbt68CeQuIdBfamzatCkXLlwAHl5+VKvVaDSafP1EPTw8GDRoEIsWLeKHH37Aw8ODTz/9FICoqChCQkJ47733CuxGPmjQIDp16oSjoyMuLi6Gx/v06UOfPn0AuHfvHtOnTycxMbHMRnx6QR19aOHlwspD0fx2KQkFD2fYwsP9+AIauzO+u5+M9ISohGR3BiMa/d1JQiMLXk58Wj38PVg/qr0RKnpo3bp1/Prrr2zbtg2AO3fusH79erZu3UpERAQDBw5k1qxZNGrUiFWrVtGiRQsCAwPznUOr1XLx4kXu3r2Lr68vtWvXLlVNGo0GCwsLVCoVMTExuLq64uzsXG7bNd1JzybkdByRt9NIzVLhZGuFfy1HBrep/I0FhKjKZMRnRE62xvnjdC6DhdIeHh75ZmAqlUqsrKwYO3YsdevWpWbNmjRr1gxbW1tmzpxZ6DmUSiXNmjUr9Dn9EoanoR9hWllZPXY5RVlwdbBhXNfy/7pCCNOS4DMi/5pO2FjGm+VCaVdXV+Li4rh+/To+Pj5Ur16dd99912jnl011hRAVhczqNKLBbUu/wLmsFkrXrl0bHx8f/vWvfz38Wjqd4ZcQQlQVco/PyMZuOsX/XUigJH+oCgX0berJ6qB2Rq9LCCFELhnxGVmtu+fQqku2YakslBZCiLInwWckarWad999lx9WLeHdrt6yUFoIIcyUTG4xgvv37zN8+HBUKhUnTpygevXquLsXvzuDniyUFkKI8iX3+Erp2rVrvPzyy3Tt2pXly5fn65hyLi5FFkoLIYSZkeArhWPHjjF48GBmz57NxIkTi5zSLwulhRDCfEjwldCmTZuYNm0aGzdu5IUXXjB1OUIIIZ6Q3OMrgZycHM6cOcOhQ4do2rSpqcsRQgjxFGTEV4yVK1fSpk0bOnbsWOC5krToEkIIYXqynKEIX3zxBZMnT2bJkiVERxfcHFZCTwghKiYJviKMGzcOtVpN/fr1+eKLLwrdy04IIUTFI8FXCJ1Oh62tLQALFiwgISGB1atX59vdQAghRMUkwfcI/b07S0tLtFotDg4OLFu2jGPHjrF161aysrJMXaIQQohSkOADjh8/TkJCAmq1GoVCgUajAXL3n9NqtdSrV4/58+cTHBzMvn37ynyXcCGEEGWnyi9nGDt2LGFhYbRv3567d++yc+dOwwapkBt+Op2Ozp07M2bMGNasWYOnpyedOnUyYdVCCCFKqkqP+A4ePMilS5c4d+4c69atIysri5EjRxqe14/s9DM4g4KC6NChAzNmzOD+/fsmqVkIIUTpVOngq1OnDr6+viQlJQGwd+9eYmNjmTt3LgCWlpaEhYWxefNmw2uuXbvGnDlzcHZ2NknNQgghSqdKX+q0sbEhKSmJixcv4u7ujkKh4Pvvv6ddu3YEBATQs2dPnJycqFGjhuE1a9euxcZG+msKIURFVaVHfPXr12fAgAFMnz6da9euoVarqVWrFlOnTuXWrVsANG3alMDAQPQNbiT0hBCiYquywacPsjFjxtCjRw+mTZvG/v37ycjIYP/+/aSnp+c7Xjq1CCFE5VCle3VqtVqUytzsX758OVeuXOHkyZM0adKE9evXm7g6IYQQZaFSB59arcbS0rLYhtJ5ww8gKiqKRo0aFfqcEEKIiq/SvqsfPnyY999/nytXrqBQKCgq3/XBpn9eH3o6nU5CTwghKqFK+c4eGhpKv379iI2N5ZtvvuHevXsoFAq0Wi2A4fe8Hh0Ryj09IYSonCpl8Hl7e7N+/XpmzpyJSqVi5cqVhhFc3pFcWFgY2dnZJq5WCCFEeaqUwdewYUOGDBlCu3bt6NOnD7du3WLVqlXAw5HcH3/8wZEjR2R5ghBCVDGVenILQGZmJrt37+bgwYOMHDmS+vXrk5aWRpMmTUxdmhBCCBOolCO+vOzs7Ojbty8vvvgis2fPpl69ely+fNnUZQkhhDCRKtGyzMXFhYMHDxITE8Phw4fp0qWLqUsSQghhIhU2+DIyMqhWrdoTH5+ens6hQ4eoX7++rM8TQogqrELe45s4cSKpqakEBATQvXt36tevX+Sxj4achJ4QQlRtFS4B3nnnHZKTkxk9ejRHjx5l+fLlHD16FKDQReqPhpyEnhBCVG0V6lJnZmYmaWlpfPDBBzRr1ow6deqwd+9etm7diqenp6HryvXr1wtsJySEEEJABRnxhYaGkp2djZ2dHfXq1WPRokVA7nq93r17U7t2bQ4ePIhOpyM2Npbvv/++wO4KQgghBFSA4NuyZQu9evUiNTUVgFGjRuHo6MiKFSuA3P3y6tSpw6FDh1AoFHh7ezNp0iTq1q1ryrKFEEKYKbMPPl9fXxo1akRUVBSQ246sb9++hIeH8+233wIwfPhwUlNTuX37NgAODg4mq1cIIYR5M/t7fB07dmTRokW8/fbbbNy4kdatW9O1a1dsbGyYMWMGkZGR/Prrr/Tq1YtatWqZulwhhBBmrsIsZ1i+fDlbt25lz549uLm5AbmTWE6fPk1OTg7Dhg0DKHbvPSGEEMKsg0+n0+XbTWHatGlcvHiRvXv3Fnm8hJ4QQojimO09PpVKxYcffkhGRobhsU8++QQnJyfefPPNfMdqNBpA9tATQgjxeGYZfPfu3SMwMJBTp07lCzM7Ozu+/PJLrl+/ztKlS9myZQsAFhYWpipVCCFEBWN2wXf58mU6derEM888w+7du7G3t8/3fM2aNVm6dClfffUVp0+fNlGVQgghKqoyv8eXnJ5NSHgckfGppGapcbK1xL+mE0PaeuHqkH8T2EOHDjFs2DAWLFjAuHHjij9vcrJhkosQQgjxpMos+CJiU1hxKJrDUUkAZKu1hudsLZXogO6N3RnfzY+W3i6sW7eO2bNns3XrVnr27FkWJQkhhBBlE3ybw66zcG8kWWoNxZ1doQAbSyVNMi/y167V/PTTTzRu3NjY5QghhBAGRg++3NC7SKZK+/iD9UVoVMx8oRHjejQ1ZilCCCFEAUad3BIRm8LCvZFPFXoAOgsrvjwUw7m4FGOWI4QQQhRg1OBbcSiaLLWmRK/NUmtYeSjamOUIIYQQBRgt+JLTszkclVTsPb3i6HTw26Uk7qRnG6skIYQQogCjBV9IeFypz6EAQk6X/jxCCCFEUYwWfJHxqfmWLJREllpL5O00I1UkhBBCFGS04EvNUhvpPCqjnEcIIYQojNGCz8nWOFv7OdlaGeU8QgghRGGMFnz+NZ2wsSzd6WwtlfjXcjRSRUIIIURBRgu+wW29Sn0OHTC4TenPI4QQQhTFaMHn5mBDt0bulHRLPIUCAhq7F2hcLYQQQhiTURewT+juh61lyfbGs7W0YHx3P2OWI4QQQhRg1OBr6e3CnEB/7Kye7rR2VkrmBPrTwsvFmOUIIYQQBRhnKmYeQR19AJ54dwZbSwvmBPobXieEEEKUpTLbj+9cXAorD0Xz26UkFOQuTtfT78cX0Nid8d39ZKQnhBCi3JT5Dux30rMJOR1H5O00UrNUONla4V/LkcFtCu7ALoQQQpS1Mg8+IYQQwpwYdXKLEEIIYe4k+IQQQlQpEnxCCCGqFAk+IYQQVYoEnxBCiCpFgk8IIUSVIsEnhBCiSpHgE0IIUaVI8AkhhKhS/h/3nGTk3cFUzgAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "pos = nx.spring_layout(graph1)\n",
    "nx.draw(graph1,pos)\n",
    "nx.draw_networkx_edge_labels(graph1,pos,nx.get_edge_attributes(graph1,'label'))\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Use only one moleculas compound"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Data(edge_attr=[64780, 3], edge_index=[2, 64780], x=[31385, 38], y=[2000])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Data(edge_attr=[166, 3], edge_index=[2, 166], x=[77, 38], y=[1])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data = data[10]\n",
    "data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### build the graph with\n",
    "train_mask, test_mask, val_mask"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "76\n"
     ]
    }
   ],
   "source": [
    "np.random.seed(10)\n",
    "# get the nodes\n",
    "nodes = data.edge_index.t().numpy()\n",
    "nodes = np.unique(list(nodes[:,0]) + list(nodes[:,1]))\n",
    "\n",
    "np.random.shuffle(nodes) # shuffle node order\n",
    "print(len(nodes))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get train test and val sizes: (70% - 15% - 15%)\n",
    "train_size = int(len(nodes)*0.7)\n",
    "test_size = int(len(nodes)*0.85) - train_size\n",
    "val_size = len(nodes) - train_size - test_size"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "53 11 12\n",
      "True\n",
      "train set\t [68 26 21  3 44 41  6 20  2 60]\n",
      "test set \t [13 73 51 49 14 67 70 33 58 43]\n",
      "val set  \t [54 11 16 36 40  0  8 29 28 64]\n"
     ]
    }
   ],
   "source": [
    "# get train test and validation set of nodes\n",
    "train_set = nodes[0:train_size]\n",
    "test_set = nodes[train_size:train_size+test_size]\n",
    "val_set = nodes[train_size+test_size:]\n",
    "\n",
    "\n",
    "print(len(train_set),len(test_set),len(val_set))\n",
    "print(len(train_set)+len(test_set)+len(val_set) == len(nodes))\n",
    "\n",
    "print(\"train set\\t\",train_set[:10])\n",
    "print(\"test set \\t\",test_set[:10])\n",
    "print(\"val set  \\t\",val_set[:10])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train mask \t tensor([0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0])\n",
      "test mask  \t tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1])\n",
      "val mask   \t tensor([1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0])\n"
     ]
    }
   ],
   "source": [
    "# build test train val masks\n",
    "\n",
    "train_mask = torch.zeros(len(nodes),dtype=torch.long, device=device)\n",
    "for i in train_set:\n",
    "    train_mask[i] = 1.\n",
    "\n",
    "test_mask = torch.zeros(len(nodes),dtype=torch.long, device=device)\n",
    "for i in test_set:\n",
    "    test_mask[i] = 1.\n",
    "    \n",
    "val_mask = torch.zeros(len(nodes),dtype=torch.long, device=device)\n",
    "for i in val_set:\n",
    "    val_mask[i] = 1.\n",
    "    \n",
    "print(\"train mask \\t\",train_mask[0:15])\n",
    "print(\"test mask  \\t\",test_mask[0:15])\n",
    "print(\"val mask   \\t\",val_mask[0:15]) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "befor\t\t Data(edge_attr=[166, 3], edge_index=[2, 166], x=[77, 38], y=[1])\n"
     ]
    }
   ],
   "source": [
    "# remove from the data what do we not use.\n",
    "\n",
    "print(\"befor\\t\\t\",data)\n",
    "data.x = None\n",
    "data.edge_attr = None\n",
    "data.y = None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "after\t\t Data(edge_index=[2, 166], test_mask=[76], train_mask=[76], val_mask=[76])\n"
     ]
    }
   ],
   "source": [
    "# add masks\n",
    "data.train_mask = train_mask\n",
    "data.test_mask = test_mask\n",
    "data.val_mask = val_mask\n",
    "\n",
    "print(\"after\\t\\t\",data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## step 2\n",
    "Execute Node2Vec to get node embeddings"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "\n",
    "model = Node2Vec(data.edge_index, embedding_dim=128, walk_length=20,\n",
    "             context_size=10, walks_per_node=10,\n",
    "             num_negative_samples=1, p=1, q=1, sparse=True).to(device)\n",
    "\n",
    "loader = model.loader(batch_size=128, shuffle=True, num_workers=4)\n",
    "optimizer = torch.optim.SparseAdam(list(model.parameters()), lr=0.01)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 10, Loss: 6.8030\n",
      "Epoch: 20, Loss: 5.2320\n",
      "Epoch: 30, Loss: 4.2469\n",
      "Epoch: 40, Loss: 3.7032\n",
      "Epoch: 50, Loss: 3.3018\n",
      "Epoch: 60, Loss: 3.0220\n",
      "Epoch: 70, Loss: 2.8173\n",
      "Epoch: 80, Loss: 2.6660\n",
      "Epoch: 90, Loss: 2.6303\n",
      "Epoch: 100, Loss: 2.5930\n"
     ]
    }
   ],
   "source": [
    "def train():\n",
    "    model.train()\n",
    "    total_loss = 0\n",
    "    for pos_rw, neg_rw in loader:\n",
    "        optimizer.zero_grad()\n",
    "        loss = model.loss(pos_rw.to(device), neg_rw.to(device))\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        total_loss += loss.item()\n",
    "    return total_loss / len(loader)\n",
    "\n",
    "\n",
    "@torch.no_grad()\n",
    "def test():\n",
    "    model.eval()\n",
    "    z = model()\n",
    "    acc = model.test(z[data.train_mask], data.y[data.train_mask],\n",
    "                     z[data.test_mask], data.y[data.test_mask],\n",
    "                     max_iter=10)\n",
    "    return acc\n",
    "\n",
    "\n",
    "for epoch in range(1, 101):\n",
    "    loss = train()\n",
    "    #acc = test()\n",
    "    if epoch % 10 == 0:\n",
    "        print(f'Epoch: {epoch:02d}, Loss: {loss:.4f}')\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "z = model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### visualize node embedding"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "# from tensor to numpy\n",
    "emb_128 = z.detach().cpu().numpy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from sklearn.decomposition import PCA\n",
    "# fit and transform using PCA\n",
    "pca = PCA(n_components=2)\n",
    "emb2d = pca.fit_transform(emb_128)\n",
    "\n",
    "\n",
    "plt.title(\"node embedding in 2D\")\n",
    "plt.scatter(emb2d[:,0],emb2d[:,1])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3\n",
    "Compute edge embedding"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "# reload AIDS dataset \n",
    "# and pick the same graph\n",
    "\n",
    "\n",
    "dataset = \"AIDS\"\n",
    "data = TUDataset(\".\", name=dataset)\n",
    "data = data[10]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Categorical edge attributes:\n",
      " [[1. 0. 0.]\n",
      " [1. 0. 0.]\n",
      " [0. 1. 0.]]\n",
      "\n",
      "\n",
      "Numerical edge attributes:\n",
      " [0, 0, 1]\n"
     ]
    }
   ],
   "source": [
    "# convert edge attributes from categorical to numerical\n",
    "edge_attr_cat = data.edge_attr.numpy()\n",
    "print(\"Categorical edge attributes:\\n\",edge_attr_cat[:3])\n",
    "\n",
    "edge_attr = []\n",
    "for i in edge_attr_cat:\n",
    "    edge_attr.append(np.nonzero(i)[0][0])\n",
    "\n",
    "print(\"\\n\\nNumerical edge attributes:\\n\",edge_attr[:3])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "# compute edge embedding\n",
    "\n",
    "edge_embedding = []\n",
    "for u,v in data.edge_index.t():\n",
    "    edge_embedding.append(np.mean([emb_128[u],emb_128[v]],0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### visualize edge embedding\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "# fit and transform using PCA\n",
    "pca = PCA(n_components=2)\n",
    "edge_emb2d = pca.fit_transform(edge_embedding)\n",
    "\n",
    "\n",
    "\n",
    "df = pd.DataFrame(dict(edge_att=edge_attr))\n",
    "colors = {0:\"red\",1:\"blue\"}\n",
    "plt.title(\"edge embedding in 2D\")\n",
    "plt.scatter(edge_emb2d[:,0],edge_emb2d[:,1],c=df.edge_att.map(colors))\n",
    "plt.show()\n",
    "\n",
    "# not so good but we are using PCA to reduce the dim from 128 to 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Step 4\n",
    "Use RandomForestClassifier with 10-fold cross validation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import cross_val_score\n",
    "from sklearn.ensemble import RandomForestClassifier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.7235294117647059"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "clf = RandomForestClassifier(max_depth=7,random_state=10)\n",
    "\n",
    "\n",
    "scores = cross_val_score(clf, edge_embedding, edge_attr, cv=10)\n",
    "np.mean(scores)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
